package com.example.devecohp;

import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.editor.markup.MarkupEditorFilter;
import com.intellij.openapi.editor.markup.MarkupEditorFilterFactory;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is registered in plugin.xml to provide line marker for the lines that violate the rule reported by the
 * auditor
 */
public class AuditorLineMarker implements LineMarkerProvider {

    /**
     * return the line marker info for the given psi element, if no line marker should be added, return null
     * @param psiElement the psi element found
     * @return the line marker info for the given psi element, if no line marker should be added, return null
     */
    @Override
    public LineMarkerInfo<?> getLineMarkerInfo(@NotNull PsiElement psiElement) {
        return null;
    }

    /**
     * add the line marker info for the required line marker needed to be added, this function is called after
     * getLineMarkerInfor is called on every psi element, an alternative way to add line marker
     * @param elements the psi elements in the page
     * @param result a buffer to store the line marker info
     */
    @Override
    public void collectSlowLineMarkers(@NotNull List<? extends PsiElement> elements, @NotNull Collection<? super LineMarkerInfo<?>> result) {
        Icon icon =IconLoader.getIcon("/META-INF/smallpluginIcon.svg", this.getClass());
        Map<Integer,List<String>> data = new HashMap<>();

        for(PsiElement psiElement: elements){
            if(psiElement instanceof PsiFile){
                if(((PsiFile) psiElement).getVirtualFile()==null){
                    continue;
                }


                PageInfo pageInfo = PageManager.getInstance(psiElement.getProject()).getPageInfo(((PsiFile) psiElement).getVirtualFile().getPath());

                if(pageInfo!=null){
                    ToolWindowInfo toolWindowInfo = PageManager.getInstance(psiElement.getProject()).getToolWindowInfo();
                    Map<RangeHighlighterEx,String> ruleElement = pageInfo.getRuleElement(toolWindowInfo.includeError.isSelected(),toolWindowInfo.includeWarning.isSelected(),toolWindowInfo.includeInfo.isSelected());
                    if(ruleElement==null){
                        return;
                    }
                    for(RangeHighlighterEx element:ruleElement.keySet()){
                        String ruleString = ruleElement.get(element);
                        Pattern squareBracketsPattern = Pattern.compile("\\[(\\d+)");
                        Matcher squareBracketsMatcher1 = squareBracketsPattern.matcher(ruleString);
                        if(squareBracketsMatcher1.find()){
                            TextRange textRange = getTextRange(psiElement, element);
                            int offset = StringUtil.lineColToOffset(psiElement.getText(),StringUtil.offsetToLineNumber(psiElement.getText(),textRange.getStartOffset()),0);
                            if(!data.keySet().contains(offset)){
                                data.put(offset,new ArrayList<>());
                            }
                            data.get(offset).add(getOnlyMessage(ruleString));
                        }
                    }
                }
                for(Integer i : data.keySet()){
                    StringBuilder html = new StringBuilder("<div>");
                    List<String> rules = data.get(i);
                    for(int j=0;j<rules.size()-1;j++){
                        html.append("<h3><span>").append(rules.get(j)).append("</span></h3>").append("<hr>");
                    }
                    html.append("<h3><span>").append(rules.get(rules.size()-1)).append("</span></h3>");
                    html.append("</div>");
                    result.add((new MyLineMarkerInfo<>(psiElement,new TextRange(i,i+1), icon,
                            (PsiElement p) -> html.toString(),
                            null, GutterIconRenderer.Alignment.LEFT)));
                }
            }
        }

    }

    /**
     * get the text range for the line represented by the RangeHighlighterEx in the psiElement
     * @param psiElement the psi element the line marker need to be added
     * @param element the RangeHighlighterEx that high lighted the line corresponding to the rule
     * @return the TextRange for the line corresponding to the rule in current file
     */
    @NotNull
    private static TextRange getTextRange(PsiElement psiElement, RangeHighlighterEx element) {
        int end = Math.min(element.getAffectedAreaEndOffset()-1,psiElement.getTextRange().getEndOffset()-1);
        String s = element.getDocument().getText();
        int start = Math.min(element.getAffectedAreaStartOffset(),end);
        while (s.charAt(start)=='\n'&&start<end){
            start=start+1;
        }
        return new TextRange(start,end);
    }

    /**
     * get the rule id from the rule message
     * @param ruleString the message for the rule
     * @param parenthesesPattern pattern to find the words in parentheses i.e. Pattern.compile("\\((.*?)\\)");
     * @return the rule id
     */
    @Nullable
    private static String getString(String ruleString, Pattern parenthesesPattern) {
        Matcher parenthesesMatcher = parenthesesPattern.matcher(ruleString);
        String wordsInParentheses = null;

        if (parenthesesMatcher.find()) {
            wordsInParentheses = parenthesesMatcher.group(1);

            if (parenthesesMatcher.find()) {

                wordsInParentheses=parenthesesMatcher.group(1);
            }
        }
        return wordsInParentheses;
    }
    public String getOnlyMessage(String ruleMessage){
        Pattern beforeBeacket = Pattern.compile("^(.*)\\(");
        Matcher matcher = beforeBeacket.matcher(ruleMessage);
        if(matcher.find()){
            String result = matcher.group(1);
            Matcher m = beforeBeacket.matcher(result);
            if(m.find()){
                return m.group(1);
            }
            return result;
        }
        return ruleMessage;
    }

    /**
     * a Line Marker Info class that mask out the line marker on the comparison pages
     */
    private static final class MyLineMarkerInfo<T extends PsiElement> extends LineMarkerInfo<T>{

        public MyLineMarkerInfo(@NotNull T element, @NotNull TextRange range, @NotNull Icon icon, @Nullable Function<? super T, @NlsContexts.Tooltip String> tooltipProvider, @Nullable GutterIconNavigationHandler<T> navHandler, @NotNull GutterIconRenderer.Alignment alignment) {
            super(element,range,icon,tooltipProvider,navHandler,alignment,()->"");
            this.updatePass=-1;
        }

        /**
         * the filter to decide which page the line marker occur
         * @return mask out the comparison pages
         */
        @Override
        public @NotNull MarkupEditorFilter getEditorFilter(){
            return MarkupEditorFilterFactory.createIsNotDiffFilter();
        }

    }

}
